CustomResourceDefinition(CRD)是 v1.7 + 新增的无需改变代码就可以扩展 Kubernetes API 的机制,用来管理自定义对象。它实际上是 ThirdPartyResources(TPR) 的升级版本,而 TPR 已经在 v1.8 中删除
一些使用场景:
- 提供/管理外部数据存储/数据库(例如 CloudSQL/RDS 实例)
- 对k8s基础资源进行更高层次的抽象(比如定义一个etcd集群)
其实crd在很多k8s周边开源项目中有使用,比如ingress-controller和众多的operator。
官方的参考文档地址:https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/
更多参考文章:https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/
官方给出的项目地址:https://github.com/kubernetes/code-generator
官方文档给出的样例是
1 | apiVersion: apiextensions.k8s.io/v1beta1 |
我们参考官方样例 , 我们以添加network为例
欲达到这个效果
1 | apiVersion: samplecrd.k8s.io/v1 |
应用这个yaml文件之后会创建一个network的资源
##例子1: 自定义network资源类型
###创建CRD
1 | apiVersion: apiextensions.k8s.io/v1beta1 |
在这个自定义资源中 , 我们指定了资源的组是samplecrd.k8s.io
, 版本是v1
, 他是scrop是namespace , 意思是这个资源是namespace的对象 , 类似于pod
###定义其中的cidr
和gateway
1 | $ tree $GOPATH/src/github.com/<your-name>/k8s-controller-custom-resource |
其中,pkg/apis/samplecrd 就是 API 组的名字,v1 是版本,而 v1 下面的 types.go 文件里,则定义了 Network 对象的完整描述
在 pkg/apis/samplecrd 目录下创建了一个 register.go 文件,用来放置后面要用到的全局变量。这个文件的内容如下所示:
1 | package samplecrd |
在 pkg/apis/samplecrd 目录下添加一个 doc.go 文件(Golang 的文档源文件)。这个文件里的内容如下所示:
1 | // +k8s:deepcopy-gen=package |
在这个文件中,你会看到 +<tag_name>[=value] 格式的注释,这就是 Kubernetes 进行代码生成要用的 Annotation 风格的注释。
其中,+k8s:deepcopy-gen=package 意思是,请为整个 v1 包里的所有类型定义自动生成 DeepCopy 方法;而+groupName=samplecrd.k8s.io
,则定义了这个包对应的 API 组的名字。
可以看到,这些定义在 doc.go 文件的注释,起到的是全局的代码生成控制的作用,所以也被称为 Global Tags。
添加 types.go 文件。顾名思义,它的作用就是定义一个 Network 类型到底有哪些字段(比如,spec 字段里的内容)。这个文件的主要内容如下所示:
1 | package v1 |
在上面这部分代码里,你可以看到 Network 类型定义方法跟标准的 Kubernetes 对象一样,都包括了 TypeMeta(API 元数据)和 ObjectMeta(对象元数据)字段。
其中的 Spec 字段,就是需要我们自己定义的部分。所以,在 networkspec 里,我定义了 Cidr 和 Gateway 两个字段。其中,每个字段最后面的部分比如json:"cidr"
,指的就是这个字段被转换成 JSON 格式之后的名字,也就是 YAML 文件里的字段名字
此外,除了定义 Network 类型,你还需要定义一个 NetworkList 类型,用来描述一组 Network 对象应该包括哪些字段。之所以需要这样一个类型,是因为在 Kubernetes 中,获取所有 X 对象的 List() 方法,返回值都是List 类型,而不是 X 类型的数组。这是不一样的。
同样地,在 Network 和 NetworkList 类型上,也有代码生成注释。
其中,+genclient 的意思是:请为下面这个 API 资源类型生成对应的 Client 代码(这个 Client,我马上会讲到)。而 +genclient:noStatus 的意思是:这个 API 资源类型定义里,没有 Status 字段。否则,生成的 Client 就会自动带上 UpdateStatus 方法。
如果你的类型定义包括了 Status 字段的话,就不需要这句 +genclient:noStatus 注释了。比如下面这个例子:
1 | // +genclient |
需要注意的是,+genclient 只需要写在 Network 类型上,而不用写在 NetworkList 上。因为 NetworkList 只是一个返回值类型,Network 才是“主类型”。
由于我在 Global Tags 里已经定义了为所有类型生成 DeepCopy 方法,所以这里就不需要再显式地加上 +k8s:deepcopy-gen=true 了。当然,这也就意味着你可以用 +k8s:deepcopy-gen=false 来阻止为某些类型生成 DeepCopy。
你可能已经注意到,在这两个类型上面还有一句+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
的注释。它的意思是,请在生成 DeepCopy 的时候,实现 Kubernetes 提供的 runtime.Object 接口。否则,在某些版本的 Kubernetes 里,你的这个类型定义会出现编译错误。这是一个固定的操作,记住即可
最后,我需要再编写的一个 pkg/apis/samplecrd/v1/register.go 文件。
在前面对 APIServer 工作原理的讲解中,我已经提到,“registry”的作用就是注册一个类型(Type)给 APIServer。其中,Network 资源类型在服务器端的注册的工作,APIServer 会自动帮我们完成。但与之对应的,我们还需要让客户端也能“知道”Network 资源类型的定义。这就需要我们在项目里添加一个 register.go 文件。它最主要的功能,就是定义了如下所示的 addKnownTypes() 方法:
1 | package v1 |
有了这个方法,Kubernetes 就能够在后面生成客户端的时候,“知道”Network 以及 NetworkList 类型的定义了。
像上面这种register.go 文件里的内容其实是非常固定的,你以后可以直接使用我提供的这部分代码做模板,然后把其中的资源类型、GroupName 和 Version 替换成你自己的定义即可
接下来,我就要使用 Kubernetes 提供的代码生成工具,为上面定义的 Network 资源类型自动生成 clientset、informer 和 lister。其中,clientset 就是操作 Network 对象所需要使用的客户端
这个代码生成工具名叫k8s.io/code-generator
,使用方法如下所示:
1 | # 代码生成的工作目录,也就是我们的项目路径 |
代码生成工作完成之后,我们再查看一下这个项目的目录结构:
1 | $ tree |
其中,pkg/apis/samplecrd/v1 下面的 zz_generated.deepcopy.go 文件,就是自动生成的 DeepCopy 代码文件。
而整个 client 目录,以及下面的三个包(clientset、informers、 listers),都是 Kubernetes 为 Network 类型生成的客户端库,这些库会在后面编写自定义控制器的时候用到。
可以看到,到目前为止的这些工作,其实并不要求你写多少代码,主要考验的是“复制、粘贴、替换”这样的“基本功”。
而有了这些内容,现在你就可以在 Kubernetes 集群里创建一个 Network 类型的 API 对象了。
我们不妨一起来实验一下
首先,使用 network.yaml 文件,在 Kubernetes 中创建 Network 对象的 CRD(Custom Resource Definition):
1 | kubectl apply -f crd/network.yaml |
这个操作,就告诉了 Kubernetes,我现在要添加一个自定义的 API 对象。而这个对象的 API 信息,正是 network.yaml 里定义的内容。我们可以通过 kubectl get 命令,查看这个 CRD:
1 | kubectl get crd |
然后,我们就可以创建一个 Network 对象了,这里用到的是 example-network.yaml:
1 | kubectl apply -f example/example-network.yaml |
通过这个操作,你就在 Kubernetes 集群里创建了一个 Network 对象。它的 API 资源路径是samplecrd.k8s.io/v1/networks
。
这时候,你就可以通过 kubectl get 命令,查看到新创建的 Network 对象:
1 | $ kubectl get network |
你还可以通过 kubectl describe 命令,看到这个 Network 对象的细节:
1 | $ kubectl describe network example-network |
当然 ,你也可以编写更多的 YAML 文件来创建更多的 Network 对象,这和创建 Pod、Deployment 的操作,没有任何区别
例子2: 自定义CronTab资源类型
创建CRD
1 | apiVersion: apiextensions.k8s.io/v1beta1 |
执行这个yaml
1 | kubectl create -f crd.yaml |
创建之后 , 可以通过API端点/apis/k8s.gmem.cc/v1/namespaces/*/crontabs/来管理自定义资源。
子资源
自定义资源可以支持/status和/scale子资源。此特性在1.11版本中处于Beta状态且默认启用。你需要在CRD中进行定义才能启用这些子资源。
scale子资源支持让其他K8S组件(例如HorizontalPodAutoscaler和PodDisruptionBudget控制器)与你的CR进行交互。kubectl scale也可以利用该子资源对CR进行扩容。
status子资源可以让你把资源的规格和状态分开。
/status
启用后, 可以在自定义的资源下的/status管理
- 数据对应资源的.status字段
- PUT /status仅仅会修改.status字段,也仅仅对该字段进行验证
- 对资源本身进行PUT/POST/PATCH操作,会忽视.status字段
- 每次修改.spec字段,都导致.metadata.generation ++
/scale
启用扩容子资源后 , CRD需要指定
- SpecReplicasPath,指定自定义资源中对应Scale.Spec.Replicas的JSON路径。必须值
- StatusReplicasPath,指定自定义资源中对应Scale.Status.Replicas的JSON路径。必须值
- LabelSelectorPath,指定自定义资源中对应Scale.Status.Selector的JSON路径。可选值,和HPA联用则必须设置
比如:
1 | apiVersion: apiextensions.k8s.io/v1beta1 |
Categories
用于指定资源所属的类别,例如all
比如:
1 | apiVersion: apiextensions.k8s.io/v1beta1 |
通过kubectl get all可以访问到上述CRD的自定义资源